﻿using Fleck;
using System.Net;
using System.Text.Json;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;


namespace Sirius.WebSocket.WebSocketAPI
{
    public static class WebSocketServerAPI
    {
        public static string WebSocketAPI_Token = JsonDocument.Parse(new StreamReader("Config.json").ReadToEnd()).RootElement.GetProperty("token").GetString();
        static string webSocketPort = JsonDocument.Parse(new StreamReader("Config.json").ReadToEnd()).RootElement.GetProperty("WSport").GetString();
        static SiriusWebSocketServer Server = new SiriusWebSocketServer("ws://0.0.0.0:" + webSocketPort);
        public static List<IWebSocketConnection> clientList = new List<IWebSocketConnection>();
        public static void InitWebSocketServer()
        {
            
            try
            {
                Server.RestartAfterListenError = true;
                Server.Start(Socket =>
                {
                    string clientSocket = Socket.ConnectionInfo.ClientIpAddress + ":" + Socket.ConnectionInfo.ClientPort;
                    Socket.OnOpen = () =>
                    {
                        Console.WriteLine("WebSocket客户端:" + clientSocket + "已连接");

                        Socket.Send("Hello!");
                    };
                    Socket.OnClose = () =>
                    {
                        Console.WriteLine("WebSocket客户端:" + clientSocket + "离开");
                    };
                    Socket.OnMessage = (Message) =>
                    {
                        if (Message.Contains(WebSocketAPI_Token))
                        {
                            clientList.Add(Socket);
                            Socket.Send("200");
                            Log.Log.LogSuc("WebSocket客户端鉴权成功！", clientSocket + "->connectSuccess!");
                        }
                        else
                        {
                            Log.Log.LogErr("WebSocket客户端鉴权失败！", clientSocket + "->connectError!");
                            Socket.Send("500");
                            Socket.Close();
                        }
                    };
                });
            }catch (Exception ex)
            {
                Log.Log.LogWar("WebSocketServer", "WebSocket端口被占用,未启动成功，若要启用请更改文件config.json的WebSocket端口" + ex.Message);
            }
        }

    }
    public class SiriusWebSocketServer : WebSocketServer
    {
        private readonly string _scheme;

        private readonly IPAddress _locationIP;

        private Action<IWebSocketConnection> _config;

        public ISocket ListenerSocket
        {
            get;
            set;
        }

        public string Location
        {
            get;
            private set;
        }

        public bool SupportDualStack
        {
            get;
        }

        public int Port
        {
            get;
            private set;
        }

        public X509Certificate2 Certificate
        {
            get;
            set;
        }

        public SslProtocols EnabledSslProtocols
        {
            get;
            set;
        }

        public SiriusWebSocketServer(string location, bool supportDualStack = true) : base(location, supportDualStack)
        {

            Uri uri = new Uri(location);
            Port = uri.Port;
            Location = location;
            SupportDualStack = supportDualStack;
            _locationIP = ParseIPAddress(uri);
            _scheme = uri.Scheme;
            Socket socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.IpTimeToLive, 1);
            if (SupportDualStack && !FleckRuntime.IsRunningOnMono() && FleckRuntime.IsRunningOnWindows())
            {
                socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, optionValue: false);
            }

            ListenerSocket = new SocketWrapper(socket);
            SupportedSubProtocols = new string[0];
        }
        public void Start(Action<IWebSocketConnection> config)
        {
            IPEndPoint ipLocal = new IPEndPoint(_locationIP, Port);
            ListenerSocket.Bind(ipLocal);
            ListenerSocket.Listen(100);
            Port = ((IPEndPoint)ListenerSocket.LocalEndPoint).Port;
            Log.Log.LogSuc("WebSocketServer监听状态", $"WebSocket服务器启动成功:{Location}");
            //FleckLog.Info($"Server started at {Location} (actual port {Port})成功");
            if (_scheme == "wss")
            {
                if (Certificate == null)
                {
                    FleckLog.Error("Scheme cannot be 'wss' without a Certificate");
                    return;
                }

                if (EnabledSslProtocols == SslProtocols.None)
                {
                    EnabledSslProtocols = SslProtocols.Tls;
                    FleckLog.Debug("Using default TLS 1.0 security protocol.");
                }
            }

            ListenForClients();
            _config = config;
        }
        private void ListenForClients()
        {
            ListenerSocket.Accept(OnClientConnect, delegate (Exception e)
            {
                FleckLog.Error("Listener socket is closed", e);
                if (RestartAfterListenError)
                {
                    FleckLog.Info("Listener socket restarting");
                    try
                    {
                        ListenerSocket.Dispose();
                        Socket socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
                        ListenerSocket = new SocketWrapper(socket);
                        Start(_config);
                        FleckLog.Info("Listener socket restarted");
                    }
                    catch (Exception ex)
                    {
                        FleckLog.Error("Listener could not be restarted", ex);
                    }
                }
            });
        }
        private void OnClientConnect(ISocket clientSocket)
        {
            if (clientSocket == null)
            {
                return;
            }

            FleckLog.Debug($"Client connected from {clientSocket.RemoteIpAddress}:{clientSocket.RemotePort.ToString()}");
            ListenForClients();
            WebSocketConnection connection = null;
            connection = new WebSocketConnection(clientSocket, _config, (byte[] bytes) => RequestParser.Parse(bytes, _scheme), (WebSocketHttpRequest r) => HandlerFactory.BuildHandler(r, delegate (string s)
            {
                connection.OnMessage(s);
            }, connection.Close, delegate (byte[] b)
            {
                connection.OnBinary(b);
            }, delegate (byte[] b)
            {
                connection.OnPing(b);
            }, delegate (byte[] b)
            {
                connection.OnPong(b);
            }), (IEnumerable<string> s) => SubProtocolNegotiator.Negotiate(SupportedSubProtocols, s));
            if (IsSecure)
            {
                FleckLog.Debug("Authenticating Secure Connection");
                clientSocket.Authenticate(Certificate, EnabledSslProtocols, connection.StartReceiving, delegate (Exception e)
                {
                    FleckLog.Warn("Failed to Authenticate", e);
                });
            }
            else
            {
                connection.StartReceiving();
            }
        }

        private IPAddress ParseIPAddress(Uri uri)
        {
            string host = uri.Host;
            if (host == "0.0.0.0")
            {
                return IPAddress.Any;
            }

            if (host == "[0000:0000:0000:0000:0000:0000:0000:0000]")
            {
                return IPAddress.IPv6Any;
            }

            try
            {
                return IPAddress.Parse(host);
            }
            catch (Exception innerException)
            {
                throw new FormatException("Failed to parse the IP address part of the location. Please make sure you specify a valid IP address. Use 0.0.0.0 or [::] to listen on all interfaces.", innerException);
            }
        }

        internal static class FleckRuntime
        {
            public static bool IsRunningOnMono()
            {
                return Type.GetType("Mono.Runtime") != null;
            }

            public static bool IsRunningOnWindows()
            {
                return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
            }
        }
    }
}

